﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using IronCow;
using Milkify.Data;
using Milkify.Interfaces;
using Milkify.Input;

namespace Milkify.Controllers
{
    class TaskListController : Controller, IController
    {
        private static Dictionary<TaskListFilter, Func<Task, bool>> sFilterFunctions;

        private static readonly Key[] sIndexedTaskShortcutGestures = new Key[10]
        {
            Key.D1, Key.D2, Key.D3, Key.D4, Key.D5, Key.D6, Key.D7, Key.D8, Key.D9, Key.D0
        };

        static TaskListController()
        {
            sFilterFunctions = new Dictionary<TaskListFilter, Func<Task, bool>>();
            sFilterFunctions.Add(TaskListFilter.Incomplete, task => { return !task.IsCompleted && !task.IsDeleted; });
            sFilterFunctions.Add(TaskListFilter.Completed, task => { return task.IsCompleted && !task.IsDeleted; });
            sFilterFunctions.Add(TaskListFilter.Deleted, task => { return task.IsDeleted; });
            sFilterFunctions.Add(TaskListFilter.All, null);
        }

        private ITaskListView mView;

        public TaskListController(ControllerContext context, Rtm rtm, IViewContainer viewContainer, IViewFactory viewFactory)
            : base(context, rtm, viewContainer)
        {
            mView = (ITaskListView)viewFactory.Create("TaskList");
            mView.TaskListSortChanged += new EventHandler(View_TaskListSortChanged);
            mView.TaskListFilterChanged += new EventHandler(View_TaskListFilterChanged);
            mView.Loaded += new RoutedEventHandler(View_Loaded);
            mView.Unloaded += new RoutedEventHandler(View_Unloaded);
            mView.NeedsRefresh = true;
        }

        public override void Execute()
        {
            // Create the commands.
            ICommand navigateToTaskCommand = new DecoupledCommand<Task>(NavigateToTask);
            ICommand completeTasksCommand = new DecoupledCommand(CompleteTasks);
            ICommand editTasksCommand = new DecoupledCommand(EditTasks);
            ICommand postponeTasksCommand = new DecoupledCommand(PostponeTasks);
            ICommand deleteTasksCommand = new DecoupledCommand(DeleteTasks);
            ICommand uncompleteTasksCommand = new DecoupledCommand(UncompleteTasks);

            // Register input bindings.
            RegisterInputBindings(navigateToTaskCommand, editTasksCommand, completeTasksCommand, postponeTasksCommand);

            // Build and assign the view data.
            TaskListViewData viewData = null;

            if (Context.CommandName == "NavigateToTaskList")
            {
                viewData = Context.CommandParameter as TaskListViewData;
                if (viewData == null)
                    throw new InvalidOperationException("The NavigateToTaskList command needs a TaskListViewData parameter.");
                // No need to refresh the view if this view data has already
                // been loaded at least once. Since we're just being executed,
                // it shouldn't be out-of-date.
                mView.NeedsRefresh = !viewData.IsLoaded;
            }
            else if (Context.CommandName == "SearchTask")
            {
                string query = Context.CommandParameter as string;
                viewData = new TaskListViewData("Search: " + query, query);
            }
            else if (Context.CommandName == "NavigateToTag")
            {
                string tag = Context.CommandParameter as string;
                string query = string.Format("tag:{0} and status:incomplete", tag);
                viewData = new TaskListViewData("Tag: " + tag, query);
            }
            else
            {
                throw new InvalidOperationException("The origin command name is unknown.");
            }

            viewData.NavigateToTaskCommand = navigateToTaskCommand;
            viewData.CompleteTasksCommand = completeTasksCommand;
            viewData.EditTasksCommand = editTasksCommand;
            viewData.PostponeTasksCommand = postponeTasksCommand;
            viewData.DeleteTasksCommand = deleteTasksCommand;
            viewData.UncompleteTasksCommand = uncompleteTasksCommand;

            mView.ViewData = viewData;

            // Filter & sort the list of tasks.
            FilterList(mView.TaskListFilter);
            SortList(mView.TaskListSort, mView.IsTaskListSortReversed);

            // Change the current view.
            ViewContainer.NavigateToView(mView);
        }

        private void RegisterInputBindings(ICommand navigateToTaskCommand, ICommand editTasksCommand, ICommand completeTasksCommand, ICommand postponeTasksCommand)
        {
            Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Start, 0, "Registering task window input bindings.");

            // Shortcuts for indexed access into the list.
            var indexedTaskShortcutCommand = new DecoupledCommand<int>(ExecuteIndexedTaskShortcutCommand, CanExecuteIndexedTaskShortcutCommand);
            for (int i = 0; i < 10; i++)
            {
                var gesture = new GenericKeyGesture(sIndexedTaskShortcutGestures[i], ModifierKeys.Control);
                var binding = new InputBinding(indexedTaskShortcutCommand, gesture);
                binding.CommandParameter = i;
                mView.InputBindings.Add(binding);
            }

            // Complete tasks
            InputBinding inputBinding = new InputBinding(completeTasksCommand, KeyboardShortcutHelper.GetKeyGesture("CompleteTaskShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Postpone tasks
            inputBinding = new InputBinding(postponeTasksCommand, KeyboardShortcutHelper.GetKeyGesture("PostponeTaskShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change due dates
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("DueDate"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskDueDateShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change repeats
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("Repeat"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskRepeatShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change time estimates
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("Estimate"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskEstimateShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change tags
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("Tags"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskTagsShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change URLs
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("Url"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskUrlShortcut"));
            mView.InputBindings.Add(inputBinding);

            // Change locations
            inputBinding = new InputBinding(CreateEditTaskParameterCommand("Location"), KeyboardShortcutHelper.GetKeyGesture("ChangeTaskLocationShortcut"));
            mView.InputBindings.Add(inputBinding);

            Milkify.Diagnostics.MilkifyTraceSources.UISource.TraceEvent(System.Diagnostics.TraceEventType.Stop, 0);
        }

        private ICommand CreateEditTaskParameterCommand(string parameterName)
        {
            return new DecoupledCommand(() =>
            {
                var tasks = mView.GetCurrentSelection();
                Redirect("TaskEdit", "EditTasks" + parameterName, tasks);
            });
        }

        private void View_TaskListSortChanged(object sender, EventArgs e)
        {
            TaskListSort sort = mView.TaskListSort;
            bool isReversed = mView.IsTaskListSortReversed;
            Properties.Settings.Default.DefaultTaskListSorting = sort.ToString();
            Properties.Settings.Default.IsDefaultTaskListSortingInversed = isReversed;
            SortList(sort, isReversed);
        }

        private void View_TaskListFilterChanged(object sender, EventArgs e)
        {
            TaskListFilter filter = mView.TaskListFilter;
            FilterList(filter);
        }

        private void View_Loaded(object sender, RoutedEventArgs e)
        {
            if (mView.NeedsRefresh)
            {
                RefreshViewData();
                mView.NeedsRefresh = false;
            }
        }

        private void View_Unloaded(object sender, RoutedEventArgs e)
        {
        }

        private void RefreshViewData()
        {
            TaskListViewData viewData = mView.ViewData as TaskListViewData;
            if (viewData == null)
                throw new InvalidOperationException("The view data hasn't been set yet, or is not a TaskListViewData.");

            viewData.RefreshAsync(Rtm);
        }

        private void Refresh(object sender, ExecutedRoutedEventArgs e)
        {
            RefreshViewData();
            e.Handled = true;
        }

        private void SortList(TaskListSort sort, bool reversed)
        {
            if (mView.ViewData != null)
            {
                TaskListViewData viewData = mView.ViewData as TaskListViewData;
                ICollectionView itemsView = CollectionViewSource.GetDefaultView(viewData.Tasks);
                ListSortDirection direction = reversed ? ListSortDirection.Descending : ListSortDirection.Ascending;
                using (itemsView.DeferRefresh())
                {
                    itemsView.SortDescriptions.Clear();
                    switch (sort)
                    {
                        case TaskListSort.Priority:
                            itemsView.SortDescriptions.Add(new SortDescription("Importance", direction));
                            break;
                        case TaskListSort.Name:
                            itemsView.SortDescriptions.Add(new SortDescription("Name", direction));
                            break;
                        case TaskListSort.Due:
                            itemsView.SortDescriptions.Add(new SortDescription("DueDateTime", direction));
                            break;
                        case TaskListSort.TimeEstimate:
                            itemsView.SortDescriptions.Add(new SortDescription("Estimate", direction));
                            break;
                    }
                    // Always sort by Priority and Name, unless it would be conflicting with
                    // the actual sorting criteria.
                    if (sort != TaskListSort.Priority)
                        itemsView.SortDescriptions.Add(new SortDescription("Importance", ListSortDirection.Ascending));
                    if (sort != TaskListSort.Name)
                        itemsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
                }
            }
        }

        private void FilterList(TaskListFilter filter)
        {
            if (mView.ViewData != null)
            {
                TaskListViewData viewData = mView.ViewData as TaskListViewData;
                Func<Task, bool> func = sFilterFunctions[filter];
                ICollectionView itemsView = CollectionViewSource.GetDefaultView(viewData.Tasks);
                if (func != null)
                {
                    itemsView.Filter = o =>
                    {
                        Task task = (Task)o;
                        return func(task);
                    };
                }
                else
                {
                    itemsView.Filter = null;
                }
            }
        }

        private void ReFilterList()
        {
            TaskListFilter filter = mView.TaskListFilter;
            FilterList(filter);
        }

        private void ExecuteIndexedTaskShortcutCommand(int index)
        {
            var viewData = mView.ViewData as TaskListViewData;
            if (viewData != null &&
                viewData.NavigateToTaskCommand != null)
            {
                ICollectionView tasksView = CollectionViewSource.GetDefaultView(viewData.Tasks);
                if (tasksView != null && tasksView.MoveCurrentToPosition(index))
                {
                    viewData.NavigateToTaskCommand.Execute(tasksView.CurrentItem);
                }
            }
        }

        private bool CanExecuteIndexedTaskShortcutCommand(int index)
        {
            var viewData = mView.ViewData as TaskListViewData;
            if (viewData != null &&
                viewData.NavigateToTaskCommand != null)
                return true;
            return false;
        }

        private void NavigateToTask(Task task)
        {
            Redirect("Task", "NavigateToTask", task);
        }

        private void CompleteTasks()
        {
            var selectedTasks = mView.GetCurrentSelection();
            foreach (var task in selectedTasks)
            {
                task.Complete();
            }

            ReFilterList();
            ViewContainer.InvalidateRefreshableViews();
        }

        private void EditTasks()
        {
            var selectedTasks = mView.GetCurrentSelection();
            Redirect("TaskEdit", "EditTasks", selectedTasks);
        }

        private void PostponeTasks()
        {
            var selectedTasks = mView.GetCurrentSelection();
            foreach (var task in selectedTasks)
            {
                task.Postpone();
            }

            ViewContainer.InvalidateRefreshableViews();
        }

        private void DeleteTasks()
        {
            var selectedTasks = mView.GetCurrentSelection();
            foreach (var task in selectedTasks)
            {
                task.Delete();
            }

            ReFilterList();
            ViewContainer.InvalidateRefreshableViews();
        }

        private void UncompleteTasks()
        {
            var selectedTasks = mView.GetCurrentSelection();
            foreach (var task in selectedTasks)
            {
                task.Uncomplete();
            }

            ReFilterList();
            ViewContainer.InvalidateRefreshableViews();
        }
    }
}
